package com.dm.library.utils;


import com.dm.library.bean.TimeConstants;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;

public class DateUtil {

    /**
     * 数字对应的汉字
     */
    public static final String[] lunarNumbers = {"零","一","二","三","四","五","六","七","八","九"};

    /**
     * 农历的月份
     */
    public static final String[] lunarMonths = {"一","二","三","四","五","六","七","八","九","十","十一","十二"};

    public static final String[] lunarDays = {	"初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
                                                "十一","十二","十三","十四","十五","十六","十七","十八","十九","廿十",
                                                "廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十"};

    public static HashMap<Integer, String[]> twelveMonthWithLeapCache = new HashMap<Integer, String[]>();
    private DateUtil(){
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    // HH:mm:ss
    private static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());


    public static int getMonthLeapByYear(int year){
        return ChineseCalendar.getMonthLeapByYear(year);
    }

    /**
     * 通过月份的索引获取当月对应的天数
     * @param year
     * 			年份
     * @param monthSway
     * 			索引从1开始
     * @param isGregorian
     * 			是否是公历
     * @return
     * 			月份包含的天数
     */
    public static int getSumOfDayInMonth(int year, int monthSway, boolean isGregorian){
        if(isGregorian){
            return getSumOfDayInMonthForGregorianByMonth(year, monthSway);
        }else{
            return getSumOfDayInMonthForLunarByMonthSway(year, monthSway);
        }
    }

    /**
     * 获取公历year年month月的天数
     * @param year
     * 			年
     * @param month
     * 			月，从1开始计数
     * @return
     * 			月份包含的天数
     */
    public static int getSumOfDayInMonthForGregorianByMonth(int year, int month){
        return new GregorianCalendar(year, month, 0).get(Calendar.DATE);
    }

    /**
     * 获取农历year年monthSway月的天数
     * @param year
     * 			年
     * @param monthSway
     * 			月，包含闰月，如闰五月，monthSway为1代表1月，5代表五月，6代表闰五月
     * @return
     */
    public static int getSumOfDayInMonthForLunarByMonthSway(int year, int monthSway){
        int monthLeap = ChineseCalendar.getMonthLeapByYear(year);
        int monthLunar = convertMonthSwayToMonthLunar(monthSway, monthLeap);
        return ChineseCalendar.daysInChineseMonth(year, monthLunar);
    }

    public static int getSumOfDayInMonthForLunarByMonthLunar(int year, int monthLunar){
        return ChineseCalendar.daysInChineseMonth(year, monthLunar);
    }

    /**
     * 根据已知的闰月月份获取monthSway指向的月份天数
     * @param year
     * 			年
     * @param monthSway
     * 			月，包含闰月，如闰五月，monthSway为1代表1月，5代表五月，6代表闰五月
     * @param monthLeap
     * 			闰月，如闰五月则为5
     * @return
     * 			monthSway指向的月份天数
     */
    public static int getSumOfDayInMonthForLunarLeapYear(int year, int monthSway, int monthLeap){
        int month = convertMonthSwayToMonthLunar(monthSway, monthLeap);
        return ChineseCalendar.daysInChineseMonth(year, month);
    }

    /**
     * 根据year的阿拉伯数字生成汉字
     * @param year
     * 			year in number format, e.g. 1970
     * @return
     * 			year in Hanzi format, e.g. 一九七零年
     */
    public static String getLunarNameOfYear(int year){

        StringBuilder sb = new StringBuilder();
        int divider = 10;
        int digital = 0;

        while(year > 0){
            digital = year % divider;
            sb.insert(0, lunarNumbers[digital]);
            year = year / 10;
        }
    //		sb.append("年");
        return sb.toString();
    }

    /**
     * 获取月份的农历中文
     * @param month
     * 			month in number format, e.g. 1
     * 			month should be in range of [1, 12]
     * @return
     * 			month in Hanzi format, e.g. 一月
     */
    public static String getLunarNameOfMonth(int month){
        if(month > 0 && month < 13){
            return lunarMonths[month - 1];
        }else{
            throw new IllegalArgumentException("month should be in range of [1, 12] month is " + month);
        }
    }

    /**
     * 获取农历的日的中文
     * @param day
     * 			day in number format, e.g. 1
     * 			day should be in range of [1, 30]
     * @return
     * 			month in Hanzi format, e.g. 初一
     */
    public static String getLunarNameOfDay(int day){
        if(day > 0 && day < 31){
            return lunarDays[day - 1];
        }else{
            throw new IllegalArgumentException("day should be in range of [1, 30] day is " + day);
        }
    }

    /**
     * 获取农历的月份的中文，可包含闰月
     * @param monthLeap
     * 			the leap month
     * 			month should be in range of [0, 12], 0 if not leap
     * @return
     */
    public static String[] getLunarMonthsNamesWithLeap(int monthLeap){

        if(monthLeap == 0){
            return lunarMonths;
        }

        if(monthLeap < -12 || monthLeap > 0){
            throw new IllegalArgumentException("month should be in range of [-12, 0]");
        }

        int monthLeapAbs = Math.abs(monthLeap);

        String[] monthsOut = twelveMonthWithLeapCache.get(monthLeapAbs);
        if(monthsOut != null && monthsOut.length == 13){
            return monthsOut;
        }

        monthsOut = new String[13];

        System.arraycopy(lunarMonths, 0, monthsOut, 0, monthLeapAbs);
        monthsOut[monthLeapAbs] = "闰" + getLunarNameOfMonth(monthLeapAbs);
        System.arraycopy(lunarMonths, monthLeapAbs, monthsOut, monthLeapAbs + 1, lunarMonths.length - monthLeapAbs);

        twelveMonthWithLeapCache.put(monthLeapAbs, monthsOut);
        return monthsOut;
    }

    /**
     * 农历中，根据闰月、月份，获取月份view应该选择显示的游标值
     * @param monthLunar
     * 			小于0为闰月。取值范围是[-12,-1] + [1,12]
     * @param monthLeap
     * 			已知的闰月。取值范围是[-12,-1] + 0
     * 			0代表无闰月
     * @return
     */
    public static int convertMonthLunarToMonthSway(int monthLunar, int monthLeap){

        if(monthLeap > 0){
            throw new IllegalArgumentException("convertChineseMonthToMonthSway monthLeap should be in range of [-12, 0]");
        }

        if(monthLeap == 0){
            return monthLunar;
        }

        if(monthLunar == monthLeap){//闰月
            return -monthLunar + 1;
        }else if(monthLunar < -monthLeap + 1){
            return monthLunar;
        }else{
            return monthLunar + 1;
        }
    }

    /**
     * 农历根据月份的游标值和闰月值，获取月份的值，负值为闰月
     * @param monthSway
     * 				在NumberPicker中的value，取值范围[1,12] + 13
     * @param monthLeap
     * 				已知的闰月。取值范围是[-12,-1] + 0
     * 				0代表无闰月
     * @return
     * 				返回ChineseCalendar中需要的month，如果是闰月，传入负值
     * 				返回值的范围是[-12,-1] + [1,12]
     */
    public static int convertMonthSwayToMonthLunar(int monthSway, int monthLeap){

        if(monthLeap > 0){
            throw new IllegalArgumentException("convertChineseMonthToMonthSway monthLeap should be in range of [-12, 0]");
        }

        if(monthLeap == 0){
            return monthSway;
        }
        //有闰月
        if(monthSway == -monthLeap + 1){//闰月
            return monthLeap;
        }else if(monthSway < -monthLeap + 1){
            return monthSway;
        }else{
            return monthSway - 1;
        }
    }

    /**
     * 农历根据年份和月份游标值，获取月份的值。负值为闰月
     * @param monthSway
     * 				农历月份view的游标值
     * @param year
     * 				农历年份
     * @return
     */
    public static int convertMonthSwayToMonthLunarByYear(int monthSway, int year){
        int monthLeap = getMonthLeapByYear(year);
        return convertMonthSwayToMonthLunar(monthSway, monthLeap);
    }


    /**
     * 将时间戳转为时间字符串
     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param millis 毫秒时间戳
     * @return 时间字符串
     */
    public static String millis2String(long millis) {
        return millis2String(millis, DEFAULT_FORMAT);
    }

    /**
     * 将时间戳转为时间字符串
     * <p>格式为format</p>
     *
     * @param millis 毫秒时间戳
     * @param format 时间格式
     * @return 时间字符串
     */
    public static String millis2String(long millis, DateFormat format) {
        return format.format(new Date(millis));
    }

    /**
     * 将时间字符串转为时间戳
     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param time 时间字符串
     * @return 毫秒时间戳
     */
    public static long string2Millis(String time) {
        return string2Millis(time, DEFAULT_FORMAT);
    }

    /**
     * 将时间字符串转为时间戳
     * <p>time格式为format</p>
     *
     * @param time   时间字符串
     * @param format 时间格式
     * @return 毫秒时间戳
     */
    public static long string2Millis(String time, DateFormat format) {
        try {
            return format.parse(time).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 将时间字符串转为Date类型
     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param time 时间字符串
     * @return Date类型
     */
    public static Date string2Date(String time) {
        return string2Date(time, DEFAULT_FORMAT);
    }

    /**
     * 将时间字符串转为Date类型
     * <p>time格式为format</p>
     *
     * @param time   时间字符串
     * @param format 时间格式
     * @return Date类型
     */
    public static Date string2Date(String time, DateFormat format) {
        try {
            return format.parse(time);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将Date类型转为时间字符串
     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param date Date类型时间
     * @return 时间字符串
     */
    public static String date2String(Date date) {
        return date2String(date, DEFAULT_FORMAT);
    }

    /**
     * 将Date类型转为时间字符串
     * <p>格式为format</p>
     *
     * @param date   Date类型时间
     * @param format 时间格式
     * @return 时间字符串
     */
    public static String date2String(Date date, DateFormat format) {
        return format.format(date);
    }

    /**
     * 将Date类型转为时间戳
     *
     * @param date Date类型时间
     * @return 毫秒时间戳
     */
    public static long date2Millis(Date date) {
        return date.getTime();
    }

    /**
     * 将时间戳转为Date类型
     *
     * @param millis 毫秒时间戳
     * @return Date类型时间
     */
    public static Date millis2Date(long millis) {
        return new Date(millis);
    }

    /**
     * 获取两个时间差（单位：unit）
     * <p>time0和time1格式都为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param time0 时间字符串0
     * @param time1 时间字符串1
     * @param unit  单位类型
     * @return unit时间戳
     */
    public static long getTimeSpan(String time0, String time1, @TimeConstants.Unit int unit) {
        return getTimeSpan(time0, time1, DEFAULT_FORMAT, unit);
    }

    /**
     * 获取两个时间差（单位：unit）
     * <p>time0和time1格式都为format</p>
     *
     * @param time0  时间字符串0
     * @param time1  时间字符串1
     * @param format 时间格式
     * @param unit   单位类型
     * @return unit时间戳
     */
    public static long getTimeSpan(String time0, String time1, DateFormat format, @TimeConstants.Unit int unit) {
        return millis2TimeSpan(Math.abs(string2Millis(time0, format) - string2Millis(time1, format)), unit);
    }

    /**
     * 获取两个时间差（单位：unit）
     *
     * @param date0 Date类型时间0
     * @param date1 Date类型时间1
     * @param unit  单位类型
     * @return unit时间戳
     */
    public static long getTimeSpan(Date date0, Date date1, @TimeConstants.Unit int unit) {
        return millis2TimeSpan(Math.abs(date2Millis(date0) - date2Millis(date1)), unit);
    }

    /**
     * 获取两个时间差（单位：unit）
     *
     * @param millis0 毫秒时间戳0
     * @param millis1 毫秒时间戳1
     * @param unit    单位类型
     *                <ul>
     *                <li>{@link TimeConstants#MSEC}: 毫秒</li>
     *                <li>{@link TimeConstants#SEC }: 秒</li>
     *                <li>{@link TimeConstants#MIN }: 分</li>
     *                <li>{@link TimeConstants#HOUR}: 小时</li>
     *                <li>{@link TimeConstants#DAY }: 天</li>
     *                </ul>
     * @return unit时间戳
     */
    public static long getTimeSpan(long millis0, long millis1, @TimeConstants.Unit int unit) {
        return millis2TimeSpan(Math.abs(millis0 - millis1), unit);
    }

    /**
     * 获取合适型两个时间差
     * <p>time0和time1格式都为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param time0     时间字符串0
     * @param time1     时间字符串1
     * @param precision 精度
     * @return 合适型两个时间差
     */
    public static String getFitTimeSpan(String time0, String time1, int precision) {
        return millis2FitTimeSpan(Math.abs(string2Millis(time0, DEFAULT_FORMAT) - string2Millis(time1, DEFAULT_FORMAT)), precision);
    }

    /**
     * 获取合适型两个时间差
     * <p>time0和time1格式都为format</p>
     *
     * @param time0     时间字符串0
     * @param time1     时间字符串1
     * @param format    时间格式
     * @param precision 精度
     * @return 合适型两个时间差
     */
    public static String getFitTimeSpan(String time0, String time1, DateFormat format, int precision) {
        return millis2FitTimeSpan(Math.abs(string2Millis(time0, format) - string2Millis(time1, format)), precision);
    }

    /**
     * 获取合适型两个时间差
     *
     * @param date0     Date类型时间0
     * @param date1     Date类型时间1
     * @return 合适型两个时间差
     */
    public static String getFitTimeSpan(Date date0, Date date1, int precision) {
        return millis2FitTimeSpan(Math.abs(date2Millis(date0) - date2Millis(date1)), precision);
    }

    /**
     * 获取合适型两个时间差
     */
    public static String getFitTimeSpan(long millis0, long millis1, int precision) {
        return millis2FitTimeSpan(Math.abs(millis0 - millis1), precision);
    }


    /**
     * 毫秒时间戳转以unit为单位的时间长度
     */
    private static long millis2TimeSpan(long millis, @TimeConstants.Unit int unit) {
        return millis / unit;
    }


    /**
     * 获取当前毫秒时间戳
     *
     * @return 毫秒时间戳
     */
    public static long getNowMills() {
        return System.currentTimeMillis();
    }

    /**
     * 获取当前时间字符串
     * <p>格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @return 时间字符串
     */
    public static String getNowString() {
        return millis2String(System.currentTimeMillis(), DEFAULT_FORMAT);
    }

    /**
     * 获取当前时间字符串
     * <p>格式为format</p>
     *
     * @param format 时间格式
     * @return 时间字符串
     */
    public static String getNowString(DateFormat format) {
        return millis2String(System.currentTimeMillis(), format);
    }

    /**
     * 获取当前Date
     *
     * @return Date类型时间
     */
    public static Date getNowDate() {
        return new Date();
    }

    /**
     * 获取与当前时间的差（单位：unit）
     * <p>time格式为yyyy-MM-dd HH:mm:ss</p>
     *
     * @param time 时间字符串
     * @param unit 单位类型
     * @return unit时间戳
     */
    public static long getTimeSpanByNow(String time, @TimeConstants.Unit int unit) {
        return getTimeSpan(getNowString(), time, DEFAULT_FORMAT, unit);
    }

    /**
     * 获取与当前时间的差（单位：unit）
     * <p>time格式为format</p>
     *
     * @param time   时间字符串
     * @param format 时间格式
     * @param unit   单位类型
     * @return unit时间戳
     */
    public static long getTimeSpanByNow(String time, DateFormat format, @TimeConstants.Unit int unit) {
        return getTimeSpan(getNowString(format), time, format, unit);
    }

    /**
     * 获取与当前时间的差（单位：unit）
     *
     * @param date Date类型时间
     * @param unit 单位类型
     * @return unit时间戳
     */
    public static long getTimeSpanByNow(Date date, @TimeConstants.Unit int unit) {
        return getTimeSpan(new Date(), date, unit);
    }

    /**
     * 获取与当前时间的差（单位：unit）
     *
     * @param millis 毫秒时间戳
     * @param unit   单位类型
     * @return unit时间戳
     */
    public static long getTimeSpanByNow(long millis, @TimeConstants.Unit int unit) {
        return getTimeSpan(System.currentTimeMillis(), millis, unit);
    }



    /**
     * 毫秒时间戳转合适时间长度
     * @param precision 精度
     *                  <ul>
     *                  <li>precision = 0，返回null</li>
     *                  <li>precision = 1，返回天</li>
     *                  <li>precision = 2，返回天和小时</li>
     *                  <li>precision = 3，返回天、小时和分钟</li>
     *                  <li>precision = 4，返回天、小时、分钟和秒</li>
     *                  <li>precision &gt;= 5，返回天、小时、分钟、秒和毫秒</li>
     *                  </ul>
     * @return 合适时间长度
     */
    private static String millis2FitTimeSpan(long millis, int precision) {
        if (millis < 0 || precision <= 0) return null;
        precision = Math.min(precision, 5);
        String[] units = {"天", "小时", "分钟", "秒", "毫秒"};
        if (millis == 0) return 0 + units[precision - 1];
        StringBuilder sb = new StringBuilder();
        int[] unitLen = {86400000, 3600000, 60000, 1000, 1};
        for (int i = 0; i < precision; i++) {
            if (millis >= unitLen[i]) {
                long mode = millis / unitLen[i];
                millis -= mode * unitLen[i];
                sb.append(mode).append(units[i]);
            }
        }
        return sb.toString();
    }


}